package com.cruise.game.common.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.cruise.game.common.api.ApiException;
import com.cruise.game.common.config.OssConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 阿里云OSS工具类
 *
 * @author cruise
 * @date 2025-02-28
 */
@Slf4j
@Component
public class OssUtils {

    // 文件类型常量
    public static final String FILE_TYPE_IMAGE = "image";
    public static final String FILE_TYPE_AUDIO = "audio";
    public static final String FILE_TYPE_VIDEO = "video";
    public static final String FILE_TYPE_DOCUMENT = "document";
    public static final String FILE_TYPE_OTHER = "other";

    // 业务文件类型常量
    public static final String FILE_TYPE_AVATAR = "avatar";
    public static final String FILE_TYPE_PRODUCT = "product";
    public static final String FILE_TYPE_ORDER_COMPLETE = "order-complete";

    // 文件类型与目录映射
    private static final Map<String, String> FILE_TYPE_DIR_MAPPING = new HashMap<>();

    // 文件类型映射
    private static final Map<String, String> CONTENT_TYPE_MAPPING = new HashMap<>();

    // 文件类型对应的扩展名
    private static final Map<String, String[]> FILE_TYPE_EXTENSIONS = new HashMap<>();

    static {
        // 初始化文件类型目录映射
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_IMAGE, "image-dir");
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_AUDIO, "audio-dir");
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_VIDEO, "video-dir");
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_DOCUMENT, "document-dir");
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_OTHER, "other-dir");

        // 初始化业务文件类型目录映射
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_AVATAR, "avatar-dir");
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_PRODUCT, "product-dir");
        FILE_TYPE_DIR_MAPPING.put(FILE_TYPE_ORDER_COMPLETE, "order-complete-dir");

        // 初始化文件类型扩展名映射
        FILE_TYPE_EXTENSIONS.put(FILE_TYPE_IMAGE, new String[] {
                ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"
        });
        FILE_TYPE_EXTENSIONS.put(FILE_TYPE_AUDIO, new String[] {
                ".mp3", ".wav", ".ogg", ".flac", ".aac", ".wma", ".m4a"
        });
        FILE_TYPE_EXTENSIONS.put(FILE_TYPE_VIDEO, new String[] {
                ".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv", ".webm"
        });
        FILE_TYPE_EXTENSIONS.put(FILE_TYPE_DOCUMENT, new String[] {
                ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", ".md", ".json", ".xml"
        });

        // 初始化常见的内容类型映射
        CONTENT_TYPE_MAPPING.put(".jpg", "image/jpeg");
        CONTENT_TYPE_MAPPING.put(".jpeg", "image/jpeg");
        CONTENT_TYPE_MAPPING.put(".png", "image/png");
        CONTENT_TYPE_MAPPING.put(".gif", "image/gif");
        CONTENT_TYPE_MAPPING.put(".bmp", "image/bmp");
        CONTENT_TYPE_MAPPING.put(".webp", "image/webp");
        CONTENT_TYPE_MAPPING.put(".svg", "image/svg+xml");

        CONTENT_TYPE_MAPPING.put(".mp3", "audio/mpeg");
        CONTENT_TYPE_MAPPING.put(".wav", "audio/wav");
        CONTENT_TYPE_MAPPING.put(".ogg", "audio/ogg");
        CONTENT_TYPE_MAPPING.put(".flac", "audio/flac");
        CONTENT_TYPE_MAPPING.put(".aac", "audio/aac");
        CONTENT_TYPE_MAPPING.put(".wma", "audio/x-ms-wma");
        CONTENT_TYPE_MAPPING.put(".m4a", "audio/mp4");

        CONTENT_TYPE_MAPPING.put(".mp4", "video/mp4");
        CONTENT_TYPE_MAPPING.put(".avi", "video/x-msvideo");
        CONTENT_TYPE_MAPPING.put(".mov", "video/quicktime");
        CONTENT_TYPE_MAPPING.put(".wmv", "video/x-ms-wmv");
        CONTENT_TYPE_MAPPING.put(".flv", "video/x-flv");
        CONTENT_TYPE_MAPPING.put(".mkv", "video/x-matroska");
        CONTENT_TYPE_MAPPING.put(".webm", "video/webm");

        CONTENT_TYPE_MAPPING.put(".pdf", "application/pdf");
        CONTENT_TYPE_MAPPING.put(".doc", "application/msword");
        CONTENT_TYPE_MAPPING.put(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        CONTENT_TYPE_MAPPING.put(".xls", "application/vnd.ms-excel");
        CONTENT_TYPE_MAPPING.put(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        CONTENT_TYPE_MAPPING.put(".ppt", "application/vnd.ms-powerpoint");
        CONTENT_TYPE_MAPPING.put(".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
        CONTENT_TYPE_MAPPING.put(".txt", "text/plain");
    }

    @Resource
    private OSS ossClient;

    @Autowired
    private OssConfig ossConfig;

    /**
     * 上传文件 - 自动检测文件类型
     *
     * @param file 文件
     * @return 访问URL
     */
    public String uploadFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ApiException("上传文件不能为空");
        }

        // 检测文件类型
        String fileType = detectFileType(file);

        // 获取对应的存储目录
        String dirKey = FILE_TYPE_DIR_MAPPING.getOrDefault(fileType, FILE_TYPE_DIR_MAPPING.get(FILE_TYPE_OTHER));
        String dirPath = ossConfig.getStorageDir(dirKey, fileType);

        // 上传文件
        return uploadFileToDir(file, dirPath);
    }

    /**
     * 上传头像
     *
     * @param file 头像文件（应为图片类型）
     * @return 访问URL
     */
    public String uploadAvatar(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ApiException("上传头像不能为空");
        }

        // 校验文件类型
        validateImageFile(file);

        // 获取头像存储目录
        String dirPath = ossConfig.getStorageDir("avatar-dir", FILE_TYPE_AVATAR);

        // 上传文件
        return uploadFileToDir(file, dirPath);
    }

    /**
     * 上传商品图片
     *
     * @param file 商品图片文件
     * @return 访问URL
     */
    public String uploadProductImage(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ApiException("上传商品图片不能为空");
        }

        // 校验文件类型
        validateImageFile(file);

        // 获取商品图片存储目录
        String dirPath = ossConfig.getStorageDir("product-dir", FILE_TYPE_PRODUCT);

        // 上传文件
        return uploadFileToDir(file, dirPath);
    }

    /**
     * 上传完单图片
     *
     * @param file 完单图片文件
     * @return 访问URL
     */
    public String uploadOrderCompleteImage(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ApiException("上传完单图片不能为空");
        }

        // 校验文件类型
        validateImageFile(file);

        // 获取完单图片存储目录
        String dirPath = ossConfig.getStorageDir("order-complete-dir", FILE_TYPE_ORDER_COMPLETE);

        // 上传文件
        return uploadFileToDir(file, dirPath);
    }

    /**
     * 校验图片文件类型
     * 
     * @param file 文件
     */
    private void validateImageFile(MultipartFile file) {
        String contentType = file.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            throw new ApiException("文件类型错误，请上传图片文件");
        }
    }

    /**
     * 上传文件到指定目录
     *
     * @param file 文件
     * @param dir  目录
     * @return 访问URL
     */
    public String uploadFileToDir(MultipartFile file, String dir) {
        if (file == null || file.isEmpty()) {
            throw new ApiException("上传文件不能为空");
        }

        // 原始文件名
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)) {
            throw new ApiException("获取文件名失败");
        }

        // 获取文件扩展名
        String extension = getFileExtension(originalFilename);

        // 确定内容类型
        String contentType = determineContentType(file, extension);

        // 生成新文件名
        String fileName = generateFileName(extension);

        // 文件路径，按日期分目录
        String filePath = generateFilePath(dir, fileName);

        try {
            // 设置文件元信息
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(contentType);
            metadata.setContentDisposition("inline;filename=" + fileName);

            // 上传文件流
            InputStream inputStream = file.getInputStream();
            PutObjectRequest putObjectRequest = new PutObjectRequest(ossConfig.getBucketName(), filePath, inputStream,
                    metadata);
            ossClient.putObject(putObjectRequest);

            log.info("文件上传成功，路径: {}, 类型: {}", filePath, contentType);

            // 返回访问URL
            return ossConfig.getBaseUrl() + "/" + filePath;
        } catch (IOException e) {
            log.error("上传文件到OSS失败: {}", e.getMessage(), e);
            throw new ApiException("上传文件失败: " + e.getMessage());
        }
    }

    /**
     * 检测文件类型
     * 
     * @param file 文件
     * @return 文件类型
     */
    private String detectFileType(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)) {
            return FILE_TYPE_OTHER;
        }

        // 获取扩展名
        String extension = getFileExtension(originalFilename).toLowerCase();
        if (StringUtils.isBlank(extension)) {
            return FILE_TYPE_OTHER;
        }

        // 根据扩展名判断文件类型
        for (Map.Entry<String, String[]> entry : FILE_TYPE_EXTENSIONS.entrySet()) {
            if (Arrays.asList(entry.getValue()).contains(extension)) {
                return entry.getKey();
            }
        }

        // 根据ContentType判断
        String contentType = file.getContentType();
        if (contentType != null) {
            if (contentType.startsWith("image/")) {
                return FILE_TYPE_IMAGE;
            } else if (contentType.startsWith("audio/")) {
                return FILE_TYPE_AUDIO;
            } else if (contentType.startsWith("video/")) {
                return FILE_TYPE_VIDEO;
            } else if (contentType.startsWith("application/") || contentType.startsWith("text/")) {
                return FILE_TYPE_DOCUMENT;
            }
        }

        return FILE_TYPE_OTHER;
    }

    /**
     * 确定内容类型
     * 
     * @param file      文件
     * @param extension 扩展名
     * @return 内容类型
     */
    private String determineContentType(MultipartFile file, String extension) {
        // 首先检查文件本身的ContentType
        String contentType = file.getContentType();

        // 如果没有ContentType或者是通用的二进制类型，则根据扩展名查找
        if (contentType == null || contentType.equals("application/octet-stream")) {
            contentType = CONTENT_TYPE_MAPPING.get(extension.toLowerCase());
        }

        // 如果还是没找到，返回通用的二进制类型
        if (contentType == null) {
            contentType = "application/octet-stream";
        }

        return contentType;
    }

    /**
     * 获取文件扩展名（带点号）
     * 
     * @param filename 文件名
     * @return 扩展名
     */
    private String getFileExtension(String filename) {
        if (filename.contains(".")) {
            return filename.substring(filename.lastIndexOf(".")).toLowerCase();
        }
        return "";
    }

    /**
     * 生成文件名
     *
     * @param extension 文件扩展名
     * @return 新文件名
     */
    private String generateFileName(String extension) {
        // 使用UUID生成新文件名
        return UUID.randomUUID().toString().replaceAll("-", "") + extension;
    }

    /**
     * 生成文件路径
     *
     * @param dir      目录
     * @param fileName 文件名
     * @return 文件路径
     */
    private String generateFilePath(String dir, String fileName) {
        // 按日期生成目录
        LocalDateTime now = LocalDateTime.now();
        String datePath = now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));

        // 拼接文件路径
        if (StringUtils.isNotBlank(dir)) {
            return dir + "/" + datePath + "/" + fileName;
        } else {
            return datePath + "/" + fileName;
        }
    }

    /**
     * 删除文件
     *
     * @param fileUrl 文件URL
     */
    public void deleteFile(String fileUrl) {
        if (StringUtils.isBlank(fileUrl)) {
            return;
        }

        // 从URL中提取文件路径
        String filePath = extractFilePathFromUrl(fileUrl);

        if (StringUtils.isBlank(filePath)) {
            log.warn("无法从URL中提取文件路径: {}", fileUrl);
            return;
        }

        try {
            // 删除文件
            log.debug("正在删除OSS文件，bucket: {}, filePath: {}", ossConfig.getBucketName(), filePath);
            ossClient.deleteObject(ossConfig.getBucketName(), filePath);
            log.info("OSS文件删除成功: {}", filePath);
        } catch (Exception e) {
            log.error("从OSS删除文件失败: {}", e.getMessage(), e);
            throw new ApiException("删除文件失败: " + e.getMessage());
        }
    }

    /**
     * 从URL中提取文件路径
     * 
     * @param fileUrl 文件URL
     * @return 文件路径
     */
    private String extractFilePathFromUrl(String fileUrl) {
        String baseUrl = ossConfig.getBaseUrl();

        // 如果URL以baseUrl开头，则去掉baseUrl部分
        if (fileUrl.startsWith(baseUrl)) {
            String path = fileUrl.substring(baseUrl.length());
            // 去掉开头的斜杠（如果有）
            if (path.startsWith("/")) {
                path = path.substring(1);
            }
            return path;
        }

        // 如果格式不匹配，尝试从URL中提取出路径部分
        try {
            // 移除协议部分
            if (fileUrl.contains("://")) {
                fileUrl = fileUrl.split("://")[1];
            }

            // 移除域名部分
            if (fileUrl.contains("/")) {
                fileUrl = fileUrl.substring(fileUrl.indexOf("/") + 1);
            }

            return fileUrl;
        } catch (Exception e) {
            log.error("从URL中提取文件路径失败: {}", fileUrl, e);
            return null;
        }
    }
}